/*
    io_gpio.c - GPIO Interface driver for io controller system
	
	Author: Benedikt Niedermayr (2013)

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

	This programm is also a result of my bachelor thesis.
	

    Note:

	This driver has to be written as a platform driver.
	In order to get compatible with the gpiolib core, we are not 
	allowed to define a driver and a device object and bind them to each other. 
    So we do the following:
	-> Register a platform_driver
	-> Register a platform_device
	-> We don't bind driver and device to a specific class!! (Because gpiolib will bind this device!!!)
	-> add gpio chip
	
	=> We need to give gpiolib a free .dev!!

	
	
*/

#include "io_gpio.h"





/* Driver specific Data */
#define MODULE_NAME "io_gpio"
#define GPIO_MINOR 	  0
const char *gpio_label="io_gpio_device";
static void io_gpio_dev_release(struct device *dev);
static ssize_t async_write_retry(struct kiocb *iocb);
static ssize_t async_read_retry(struct kiocb *iocb);
static int cnt = 0;


/* 
 * We will need the fileops for async_read, async_write
 */
static struct device_driver io_gpio_driver = {
	.name = "io_gpio_driver",
	.bus = &platform_bus_type,
};


struct platform_device io_gpio_device = {
	.name	= "io_gpio_device",
	.id		= 0,
	.dev = {
		.release = io_gpio_dev_release,
	}
};

static DECLARE_COMPLETION(dev_obj_is_free);
static void io_gpio_dev_release(struct device *dev)
{
	complete( &dev_obj_is_free );
}


/*
 * We do also support sysfs attributes!
 * change the current used gpio pin.
 */
static ssize_t set_async_pin(struct device *dev, struct device_attribute *attr, char *buf)
{
	sprintf(buf, "%s\n", &async_gpio_pin[0]);
	return strlen(buf)+1;
}


static ssize_t get_async_pin(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
	strlcpy(&async_gpio_pin[0], buf, count);
	return strlen(buf)+1;
}
static DEVICE_ATTR(async_pin, 0644, set_async_pin, get_async_pin);




/** irq_bottom_half() - Interrupt functionality 
 * (But with no interrupt context -> instead make use of kthreats).
 * To get gpiolib compatibility we simply call the gpiolib irq_handler!
 */
void irq_bottom_half(struct work_struct *work) 
{
	printk("[%s] irq_bottom_half start...\n", MODULE_NAME);
}



/** send_io_func_command() - io controller communication function.
 * Create and sends function-commands to the io-processor.
 * The command will be created with io_com core functions.
 * @iop: pointer to io_com struct containig all global information
 * @pin: GPIO Pin id from io processor (char '0' - '19')
 * @tr_mode: Specifies and synchronous or asynchronous call 
 */
int send_io_func_command(struct io_com *iop, struct kiocb *iocb, char *pin, char direction, char value, int tr_mode)
{
	struct cmd *new_cmd;

	/* new command list element */ 
	new_cmd = kmalloc(sizeof(*new_cmd), GFP_KERNEL);
	if (new_cmd == NULL) {
		return -ENOMEM;
	  }

	/* Create command */
	INIT_LIST_HEAD(&new_cmd->root.list);
	add_param_to_list(1, pin, new_cmd, strlen(pin));
	
	if(direction == DIR_OUT)
		add_param_to_list(2, &value, new_cmd, 1);
	
	create_params_string(new_cmd);
	create_func_package(cnt, IO_GPIO, direction, new_cmd);
	add_cmd_to_list(new_cmd, global_pointer);
 
	mutex_lock(&global_pointer->cnt_lock);
		cnt = (cnt + 1) % MAX_COMMAND_ID;
	mutex_unlock(&global_pointer->cnt_lock); 

	if(tr_mode == TR_MODE_SYNC) {
		sync(global_pointer);
		/* Check return value from I/O Controller and make spezific
     	 * return value****************************************************************************/
		if(direction == DIR_OUT){
			//Return 0->ok , other values-> Error	
			if(new_cmd->corresp->answer_payload[0] == '0') {
				return 0;
			} else {
				return -1;
			}	
						
		} else {
			if(new_cmd->corresp->answer_payload[0] == '0') {
				return 0;
			} else if(new_cmd->corresp->answer_payload[0] == '1') {
				return 1;
			} else {
				printk("[%s] wrong character=>%c\n", MODULE_NAME, new_cmd->corresp->answer_payload[0]);
					return -EIO;			
			}
			// Return pin state	
		}
		
	} else {//ASYNC_MODE
		new_cmd->user_buf = (void *)iocb->ki_buf; 
		if(direction == DIR_OUT) {
			iocb->ki_retry = async_write_retry;
		} else {
			iocb->ki_retry = async_read_retry;
		}		
		mutex_lock(&global_pointer->iocb_lock);
		iocb_kick[global_pointer->iocb_cnt] = iocb;
		global_pointer->iocb_cnt++;		
		mutex_unlock(&global_pointer->iocb_lock);
		iocb->private = (void *)new_cmd;
		return -EIOCBRETRY;
				
	}
	
	
	
	
		
}

/** send_io_sys_command() - io controller communication function 
 * sends functions commands to io processor.
 * The command will be created with io_com core functions.
 * @iop: pointer to io_com struct containig all global information
 * @pin: GPIO Pin id from io processor (char '0' - '19')
 * @mode: Used for setting a GPIO's mode (0-> Pull_down, 1->pull_up)
 * @tr_mode: Specifies and synchronous or asynchronous call 
 */
int send_io_sys_command(struct io_com *iop, struct kiocb *iocb, char *pin, char mode, int tr_mode)
{
	struct cmd *new_cmd;

	/* new command list element */ 
	new_cmd = kmalloc(sizeof(*new_cmd), GFP_KERNEL);
	if (new_cmd == NULL) {
		return -ENOMEM;
	  }

	/* Create command */
	INIT_LIST_HEAD(&new_cmd->root.list);
	add_param_to_list(1, pin, new_cmd, strlen(pin));
	add_param_to_list(2, &mode, new_cmd, 1);
	create_params_string(new_cmd);
	if(create_sys_package(cnt, INIT_GPIO, new_cmd))
		return -EIO;

	add_cmd_to_list(new_cmd, global_pointer);

	mutex_lock(&global_pointer->cnt_lock);
		cnt = (cnt + 1) % MAX_COMMAND_ID;
	mutex_unlock(&global_pointer->cnt_lock); 


	if(tr_mode == TR_MODE_SYNC) {
		sync(global_pointer);
		/* Check return value from I/O Controller and make spezific
	   return value****************************************************************************/
		/* Return new_cmd->corresp->answer_payload but we need integer!!!!! */
		return 0;	
	} else {
		new_cmd->user_buf = (void *)iocb->ki_buf; 	
		iocb->ki_retry = async_write_retry;		
		mutex_lock(&global_pointer->iocb_lock);
		iocb_kick[global_pointer->iocb_cnt] = iocb;
		global_pointer->iocb_cnt++;		
		mutex_unlock(&global_pointer->iocb_lock);
		iocb->private = (void *)new_cmd;
		return -EIOCBRETRY;			
	}

	

	
}


/* set gpio direction. 
 * function handler for gpiolib
 */
static int io_gpio_direction_input(struct gpio_chip *chip, unsigned off)
{
	char tmp_off[3];	
	int i = (int)off;
	i += 12;

	/*Send sys command -> set gpio pin state in pull down mode(pull down is default) */
	/* Create command */
	if(DEBUG == 1) {
		printk("gpio direction input called!\n");
		printk("io_gpio_direction_input(offset=%d)",(int)off);
	}
	snprintf(&tmp_off[0], 3, "%d", i);
	tmp_off[2]='\0';
	if(DEBUG == 1)
		printk("tmp buf=%s\n", &tmp_off[0]);

	return send_io_sys_command(global_pointer, NULL, &tmp_off[0], INPUT_PULLDOWN, TR_MODE_SYNC);

		
}



/* set gpio direction. 
 * function handler for gpiolib
 * Parameter Val is not needed
 * Pin gets always initialized with low_val
 */
static int io_gpio_direction_output(struct gpio_chip *chip, unsigned off, int val)
{
	char tmp_off[3];	
	int i = (int)off;
	i += 12;

	/*Send sys command -> set gpio pin state in pull down mode(pull down is default) */
	/* Create command */
	if(DEBUG == 1) {
		printk("gpio direction output called!\n");
		printk("io_gpio_direction_input(offset=%d)",(int)off);
	}
	
	snprintf(&tmp_off[0], 3, "%d", i);
	tmp_off[2]='\0';

	if(DEBUG == 1)
		printk("tmp buf=%s\n", &tmp_off[0]);

	return send_io_sys_command(global_pointer, NULL, &tmp_off[0], OUTPUT_PUSH_PULL, TR_MODE_SYNC);
}



/* get gpio value. 
 * function handler for gpiolib
 */
static int io_gpio_get_value(struct gpio_chip *chip, unsigned off)
{
	char tmp_off[3];	
	int i = (int)off;
	i += 12;

	/*Send sys command -> set gpio pin state in pull down mode(pull down is default) */
	/* Create command */
	if(DEBUG == 1) {
		printk("io_gpio_get_value called!\n");
		printk("io_gpio_direction_input(offset=%d)",(int)off);
	}	
	snprintf(&tmp_off[0], 3, "%d", i);
	tmp_off[2]='\0';

	if(DEBUG == 1)
		printk("tmp buf=%s\n", &tmp_off[0]);	
	
	
	return send_io_func_command(global_pointer, NULL,&tmp_off[0], DIR_IN, '0', TR_MODE_SYNC);	
}



/* set gpio value. 
 * function handler for gpiolib
 */
static void io_gpio_set_value(struct gpio_chip *chip, unsigned off, int val)
{
	char tmp_off[3];	
	char tmp_val;	
	int i = (int)off;
	i += 12;


	if(val == 1) {
		tmp_val = '1';	
	} else {
		tmp_val ='0';	
	}

	/*Send sys command -> set gpio pin state in pull down mode(pull down is default) */
	/* Create command */
	if(DEBUG == 1) {
		printk("io_gpio_set_value called!\n");
		printk("io_gpio_direction_input(offset=%d)",(int)off);
		printk("io_gpio_direction_input(value=%c)",tmp_val);
	}

	snprintf(&tmp_off[0], 3, "%d", i);
	tmp_off[2]='\0';

	if(DEBUG == 1)
		printk("tmp buf=%s\n", &tmp_off[0]);
	send_io_func_command(global_pointer, NULL ,&tmp_off[0], DIR_OUT, tmp_val, TR_MODE_SYNC);  	
	return;
}


/* add the initialized 
 * gpio_chip
 */
int set_gpio_chip(struct io_gpio *io_gpio_data)
{
	int ret;	
	struct gpio_chip *tmp = &io_gpio_data->io_gpio_chip;

	tmp->direction_input  = io_gpio_direction_input;
	tmp->direction_output = io_gpio_direction_output;
	tmp->get = io_gpio_get_value;
	tmp->set = io_gpio_set_value;
	tmp->can_sleep = 1;

	tmp->base = io_gpio_data->gpio_base;
	tmp->ngpio = io_gpio_data->num_gpios;
	tmp->label = gpio_label;

	tmp->dev = io_gpio_data->dev;
	tmp->owner = THIS_MODULE;

	/* register the gpio chip */
	printk("Adding gpio_chip...\n");
	ret = gpiochip_add(tmp);
	return ret; 
}





/* file operations on /dev/io_gpio_dev. Used to support
 * asynchrounus io. 
 */


static ssize_t async_write_retry( struct kiocb *iocb )
{
	int res;
	struct cmd *new_cmd =  (struct cmd *)iocb->private; 
	if(DEBUG == 1)
		printk( ".......aio_gpio_retry(%p)\n", iocb);
	
	if(new_cmd->corresp == NULL) {
		printk("Error while getting aligned return value!\n");
		return -EIO;	
	}

	/* We don't need to copy to user... This is a write callback function
     * We only return the spezific value (negative -> ERROR, positive -> bytes written)
     */
	
	sscanf((const char *)&new_cmd->corresp->answer_payload[0],"%d", &res);

	/* If something happens wrong, we inform the userspace via return value */
	if(new_cmd->corresp == NULL) {
		printk("error while getting aligned answer payload!\n");
		goto res_error_null;	
	}

	if(SIMULATE != 1) {
		if(new_cmd->corresp->answ_len != 1)
			goto res_error;
		if(res < 0)
			goto res_error;
	}
	
	
	if(DEBUG == 1) {
		if(SIMULATE == 1) {
			printk("Answer from io controller (for requested write) is %c\n", (new_cmd->corresp->answer_payload[0]-19));	// answer_payload[0] is C -> ascii C-19 = 0
		} else {
			printk("Answer from io controller (for requested write) is %c\n", new_cmd->corresp->answer_payload[0]);		
		}			
	}	

	list_del(&new_cmd->corresp->list);	
	kfree(new_cmd->corresp);
	list_del(&new_cmd->list);
	kfree(new_cmd);
	return 1; //new_cmd->payload_len;

res_error:
	printk("answer_payload =>%s", new_cmd->corresp->answer_payload);
res_error_null:
	printk("aio_gpio_retry error\n");
	list_del(&new_cmd->corresp->list);	
	kfree(new_cmd->corresp);
	list_del(&new_cmd->list);
	kfree(new_cmd);
	return -EAGAIN;
}

static ssize_t async_read_retry( struct kiocb *iocb )
{
	int bytes_not_read = 0, bytes_to_read = 0;
	struct cmd *new_cmd =  (struct cmd *)iocb->private; 
	
	if(DEBUG == 1)	
		printk( ".......aio_i2c_retry(%p)\n", iocb);

	if(new_cmd->corresp == NULL) {
		printk("Error while getting aligned answer payload!\n");
		return -EIO;	
	}

	/* We can easily use copy_to_user because we are in a
     * userpsace process kontext
     */
	bytes_to_read = min((int)new_cmd->corresp->answ_len, (int)iocb->ki_nbytes);
	
	if(DEBUG == 1)
		printk("bytes_to_read:%d\n",bytes_to_read);

	/* we give back the return value from io_processor 
     * -> return requested payload 
     */
	
	bytes_not_read = copy_to_user(new_cmd->user_buf, new_cmd->corresp->answer_payload, bytes_to_read);   // answer_payload[0] is C -> ascii C-19 = 0


	list_del(&new_cmd->corresp->list);	
	kfree(new_cmd->corresp);
	list_del(&new_cmd->list);
	kfree(new_cmd);
	return bytes_to_read - bytes_not_read;
}

/** io_async_read() - GPIO asynchrounus systemcall. 
 *  This function provides async_io access from userspace. 
 *  @iocb:	Async_io file struct. Contains all data(userpace address, offset, number of bytes) overgiven from userpsace.
 *  @buf:	Should contain the (virtual)address of the tranfser buffer in userspace.
 *  @count: Should contain the number of bytes to tranfser.
 *  @offset:Should contain the offset in tranfser buffer.
 *  
 * BUG:
 * The overgiven parameters (buf and count) both containing wrong values
 * for Userspace buffer address and bytes to transfer...
 * Instead I take Data from the Async_io file struct (iocb->ki_nbytes and iocb->ki_buf)!!
 */
static ssize_t io_async_read(struct kiocb *iocb, char __user *buf, unsigned long count, loff_t offset)
{
	if(DEBUG == 1) {
		printk( "driver_aioread(%p,%p,%d, bytes:%d )\n", iocb, buf, (int)count, iocb->ki_nbytes);
		printk("kiocb->ki_nbytes: %d\n", iocb->ki_nbytes);
		printk("kiocb->ki_buf: %p\n", iocb->ki_buf);
		printk("kiocb->ki_left: %d\n", iocb->ki_left);
	}
	return send_io_func_command(global_pointer, iocb, &async_gpio_pin[0], DIR_IN, '0', TR_MODE_ASYNC);
}


/** io_async_write() - GPIO asynchrounus systemcall. 
 *  This function provides async_io access from userspace. 
 *  @iocb:	Async_io file struct. Contains all data(userpace address, offset, number of bytes) overgiven from userpsace.
 *  @buf:	Should contain the (virtual)address of the tranfser buffer in userspace.
 *  @count: Should contain the number of bytes to tranfser.
 *  @offset:Should contain the offset in tranfser buffer.
 *  
 * BUG:
 * The overgiven parameters (buf and count) both containing wrong values
 * for Userspace buffer address and bytes to transfer...
 * Instead I take Data from the Async_io file struct (iocb->ki_nbytes and iocb->ki_buf)!!
 */
static ssize_t io_async_write(struct kiocb *iocb, char __user *buf, unsigned long count, loff_t offset)
{

	/* GPIO STRING FROM USERPACE
	 * Set pin 9 to output (system command): {SYSTEM_FLAG}{MODE}	<----async write (mode can be 0 for input and 1 for output)  Example: S1 
	 * Set pin 9 to High (func command):	 {FUNC FLAG}{VALUE}		<----async write 
     */

	char *payload;
	char value;
	if(DEBUG == 1) {	
		printk( "driver_aiowrite(%p,%p,%d, bytes:%d )\n", iocb, buf, (int)count, iocb->ki_nbytes);
		printk("kiocb->ki_nbytes: %d\n", iocb->ki_nbytes);
		printk("kiocb->ki_buf: %p\n", iocb->ki_buf);
		printk("kiocb->ki_left: %d\n", iocb->ki_left);
	}

	/* get payload data from userpsace */
	payload = new_char(MAX_DATA_LEN);
	if (copy_from_user(payload, iocb->ki_buf, iocb->ki_nbytes)) {
		kfree(new_char);
		return -EFAULT;

	}
	if(DEBUG == 1)
		printk("payload => %s\n", payload);

	if(*payload == FFUNC) {
		payload++;		
		value = *payload; 

		if(DEBUG == 1)
			printk("value=%c\n", value);

		return send_io_func_command(global_pointer, iocb, &async_gpio_pin[0], DIR_OUT, value, TR_MODE_ASYNC);
	} else if (*payload == FSYS) {
		payload++;

		if(*payload == '0')//input
		return send_io_sys_command(global_pointer, iocb, &async_gpio_pin[0], INPUT_PULLDOWN, TR_MODE_ASYNC);
		
		if(*payload == '1')//output
		return send_io_sys_command(global_pointer, iocb, &async_gpio_pin[0], OUTPUT_PUSH_PULL, TR_MODE_ASYNC);

	} else {
		printk("ERROR: Wrong async gpio string!\n");
		return -EIO;	
	}
	return -EIO;
	
}



static int io_gpio_open(struct inode *inode, struct file *file) {return 0;}
static int io_gpio_release(struct inode *inode, struct file *file) { return 0;}
static const struct file_operations fops = {
	.owner		= THIS_MODULE,
	.open		= io_gpio_open,
	.release	= io_gpio_release,
	.aio_read	= io_async_read,
	.aio_write  = io_async_write,
};


static int __init io_gpio_init(void)
{	
	int ret;
	struct io_gpio *io_gpio_data;

	io_gpio_data = kmalloc(sizeof(*io_gpio_data), GFP_KERNEL);
	if(io_gpio_data == NULL)
		return -ENOMEM;


	if(register_chrdev(MAJOR_IO_GPIO, "io_gpio_dev", &fops) == 0) {
		ret = driver_register(&io_gpio_driver);	
		platform_device_register( &io_gpio_device ); // This device is for the gpiolib
		io_gpio_data->dev = &io_gpio_device.dev;
		/* We don't need to use device_bind_driver(), driver-device binding will happen
	     * automatically!!
		 */
	} else {
		printk("[%s] Error while getting chrdev!\n", MODULE_NAME);
		goto free_data;	
	}

	/* Now support the Udev/Mdev functionality of
     * automatic creating dev-files. This will add an additional device
	 */
	io_gpio_data->class_dev = device_create(global_pointer->io_class, NULL, MKDEV(MAJOR_IO_GPIO, 0) ,NULL, "io_gpio_dev");
	ret = device_create_file(io_gpio_data->class_dev, &dev_attr_async_pin);	
		if(ret) {
			printk("[%s] Error creating sysfs attribute!\n", MODULE_NAME);
			return ret;
		}
	/* connect the global structure */
	if(global_pointer == NULL) {
		printk("[%s] global structure not found!\n", MODULE_NAME);
		goto free_data;	
	}

	/* Connect global pointer with initialized data */
	global_pointer->io_gpio_pointer = io_gpio_data;
	

	/* SET THE GPIO CHIP STRUCTURE
     * and register a gpio_chip 
     */ 
	io_gpio_data->gpio_base = GPIO_BASE_NR;
	io_gpio_data->num_gpios	= NUM_OF_GPIOS;
	if(set_gpio_chip(io_gpio_data)) {
		/* dev_err() is not save at this point */		
		printk("[%s] Error setting gpio_chip!\n", MODULE_NAME);
		goto free_data;	
	}

	/* Initialize work object function irq_bottom_half() */
	INIT_WORK(&gpio_bottom_half, &irq_bottom_half);	
	

	printk("[%s] driver loaded\n", MODULE_NAME);
	printk("gpio starts at gpio%d\n", GPIO_BASE_NR);
	return 0;

free_data:
	kfree(io_gpio_data);
	return -EIO;
}



static void __exit io_gpio_exit(void)
{
	struct io_gpio *io_gpio_data;
	int ret;

	if(global_pointer->io_gpio_pointer == NULL)
		return;
	io_gpio_data = global_pointer->io_gpio_pointer;
	
	ret = gpiochip_remove(&io_gpio_data->io_gpio_chip);
	if (ret) {
		printk("%s failed, %d\n", "gpiochip_remove()", ret);
		return;
	}

	device_remove_file(io_gpio_data->class_dev, &dev_attr_async_pin);
	device_destroy(global_pointer->io_class, MKDEV(MAJOR_IO_GPIO, 0));
	device_release_driver( &io_gpio_device.dev );
	platform_device_unregister( &io_gpio_device );
	driver_unregister(&io_gpio_driver);
	unregister_chrdev(MAJOR_IO_GPIO,"io_gpio_dev");
	//wait_for_completion( &dev_obj_is_free );
	printk("[%s] driver unloaded\n", MODULE_NAME);
}

module_init( io_gpio_init );
module_exit( io_gpio_exit );

MODULE_AUTHOR("Benedikt Niedermayr");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Additional gpio userspace interface driver for i/o-controller");
MODULE_SUPPORTED_DEVICE("none");
